import java.util.Arrays;
import java.util.Hashtable;
import java.util.regex.Matcher;
import java.util.regex.Pattern;



public class BaseMessage {
	
	MessageType type = MessageType.UNKNOWN;
	MessageDirection direction = MessageDirection.UNKNOWN;
	int numFields = 0;
	String restOfMessage = "";
	Hashtable<String, String> arguments = new Hashtable<String, String>();
	String argNames;
	String[] argNamesArray;
	
	//regex helpers
	private String arg="((?:[a-z0-9_&%:.,-]*))";	// any argument in message
	private String ws="(\\s+)";	// White Space
	private String argPlusWS=arg+ws;	// White Space
	
	public static void main(String[] args)
	{
		//this is just for testing
		BaseMessage test = new BaseMessage();
		
//		test.parseBaseMessage("BYE 0x01 0");
//		test.parseBaseMessage("get-backup-msd 0x0A 4 US-ASCII keyword1 192.168.1.1 1234");
		test.parseBaseMessage("register 0x0A 19 US-ASCII 213322 34343 jared.com 192.168.1.1 65432 192.168.1.2 23456 asm test 1242 1242.4 key1,key2,key3 more stuff to add later on");
		test.parseMessageFurther();
		System.out.println(test.arguments.toString());
		
		BaseMessage test2 = new BaseMessage();
		//[invalidate] [0x0A] [7] [char-set] [keyword]
		//[cached IP] [cached port] [IP] [port] [inversion flag][\n]
		test2.parseBaseMessage("invalidate 0x0A 7 US-ASCII mykeyword 127.168.1.1 34332 200.127.34.21 45435 1");
		test2.parseMessageFurther();
		System.out.println(test2.arguments.toString());
		
		BaseMessage test3 = new BaseMessage();
		//[search] [0x0A] [3] [char-set] [parameter] [client port
		test3.parseBaseMessage("search 0x0A 3 US-ASCII keyword1:keyword2:keyword3:keyword4&key5:key6&key7%yes:no 3423");
		test3.parseMessageFurther();
		System.out.println(test3.arguments.toString());
		
		BaseMessage test4 = new BaseMessage();
		//[ext-search] [0x0A] [5] [char-set] [parameter] [IP] [port] [inversion flag][\
		test4.parseBaseMessage("ext-search 0x0A 5 US-ASCII keyword1:keyword2:keyword3:keyword4&key5:key6&key7%yes:no 123.345.214.234 3423 1");
		test4.parseMessageFurther();
		System.out.println(test4.arguments.toString());
		
		BaseMessage test5 = new BaseMessage();
		//[get-backup-msd] [0x0A] [4] [char-set] [keyword] [IP] [port][\n]
		test5.parseBaseMessage("get-backup-msd 0x0A 4 US-ASCII keyword1 123.345.214.234 3423");
		test5.parseMessageFurther();
		System.out.println(test5.arguments.toString());
		

		BaseMessage test6 = new BaseMessage();
//		[remote-register] [message-direction] [10] [char-set] [URS-
//		   Identifier] [keyword] [host URI] [expiration time] [Lat] [Long]
//		   [network type] [stream type] [inversion flag][\n]
		test6.parseBaseMessage("remote-register 0x0A 10 US-ASCII urs-id1 keyword1 jared.com 12323 lat long nettype streamtype 1");
		test6.parseMessageFurther();
		System.out.println(test6.arguments.toString());

	}

	public BaseMessage()
	{
		
	}

	public boolean parseBaseMessage(String txt)
	{
		if(txt == null)
			return false;
		txt = txt.trim();
		
		String messageType="((?:[a-z][a-z-]+))";	// Word 1
	    String ws1="(\\s+)";	// White Space 1
	    String dirPrefix="(0x)";	// Integer Number 1
	    String dirDigits="(..)";	// Any Single Character 1
	    String ws2="(\\s+)";	// White Space 2
	    String numFieldsStr="(\\d+)";	// Integer Number 2
	    String restOfMessage="(.*)";	// Non-greedy match on filler

	    Pattern p = Pattern.compile(messageType+ws1+dirPrefix+dirDigits
	    		+ws2+numFieldsStr+restOfMessage,
	    		Pattern.CASE_INSENSITIVE | Pattern.DOTALL);
	    
	    Matcher m = p.matcher(txt);
	    if (m.find())
	    {
	        String messageType_m=m.group(1);
	        String ws1_m=m.group(2);
	        String dirPrefix_m=m.group(3);
	        String dirDigits_m=m.group(4);
	        String ws2_m=m.group(5);
	        String numFields_m=m.group(6);
	        String restOfMessage_m=m.group(7);
//	        System.out.print("("+messageType_m.toString()+")"+"("+ws1_m.toString()+")"
//	        		+"("+dirPrefix_m.toString()+")"+"("+dirDigits_m.toString()+")"
//	        		+"("+ws2_m.toString()+")"+"("+numFields_m.toString()+")"
//	        		+"("+restOfMessage_m.toString()+")"+"\n");
	        
	        this.type = parseMessageType(messageType_m);
	        this.direction = parseDirection(dirDigits_m);
	        this.numFields = parseNumFields(numFields_m);
	        this.restOfMessage = restOfMessage_m.trim();
	        
	        return true;
	    }
	    else
	    {
	    	return false;
	    }
	}
	
	public boolean parseMessageFurther()
	{
		boolean result = true;
		switch(type) {
	     case UNKNOWN:
	    	 //do nothing
	       break;
	     case BYE:
	    	 //do nothing
	       break;
	     case CHECK:
	    	 argNames = "[check] [0x01] [2] [char-set] [keyword]";
	       break;
	     case CHECK_RESPONSE:
	    	 argNames = "[check-response] [0x03] [1] [status]";
	       break;
	     case REGISTER:
	    	 if(numFields==19)
	    		 argNames = "[register] [0x0A] [19] [char-set] [expiration time] [start time] [URS Identifier] [channel IP] [channel port] [unicast IP] [unicast port] [channel scope] [Geographic CN] [Lat] [Long] [keyword list] [network type] [source IP] [stream type] [preferred application] [CLI argument] [mime-type]";
	    	 else if(numFields==17)
	    		 argNames = "[register] [0x01] [17] [char-set] [expiration time] [URS-Identifier] [channel IP] [channel port] [unicast IP] [unicast port] [channel scope] [Geographic CN] [Lat] [Long] [network type] [source IP] [stream type] [preferred application] [CLI argument] [mime-type]";
	       break;
	     case REGISTER_STATUS:
	    	 argNames = "[register-status] [0x03] [1] [status]";
	       break;
	     case QUERY:
	    	 argNames = "[query] [0x01] [2] [char-set] [URS-Identifier]";
	       break;
	     case QUERY_RESPONSE:
	    	 if(numFields==1)
	    		 argNames = "[query-response] [0x03] [1] [null]";
	    	 else if(numFields==17)
	    		 argNames = "[query-response] [0x03] [17] [char-set] [channel IP] [channel port] [Geographic CN] [Lat] [Long] [unicast IP] [unicast port] [channel scope] [URS-Identifier] [expiration time] [network type] [source IP] [stream type] [preferred application] [CLI argument] [mime-type]";
	       break;
	     case REQUEST:
	    	 argNames = "[request] [0x01] [1] [mcast_paramdesignated]";
	       break;
	     case REQUEST_RESPONSE:
	    	 if(numFields==3)
	    		 argNames = "[request-response] [message-direction] [3] [designated] [msd IP] [msd port]";
	    	 else if(numFields==11)
	    		 argNames = "[request-response] [message-direction] [11] [mcast_param] [cmcast IP] [cmcast port] [pmcast IP] [pmcast port] [pmcast source IP] [msd-local IP] [msd-local port] [msd IP] [msd port] [IGMP version]";
	       break;
	     case UNICAST_UPDATE:
	    	 argNames = "[unicast-update] [0x02] [3] [ID hash] [msd IP] [msd port]";
	       break;
	     case UNICAST_UPDATE_STATUS:
	    	 argNames = "[unicast-update-status] [0x09] [1] [status]";
	       break;
	     case RESYNC:
	    	 //no extra fields
	       break;
	     case RESYNC_STATUS:
	    	 if(numFields==1)
	    		 argNames = "[resync-status] [0x09] [1] [status]";
	    	 else if(numFields==3)
	    		 argNames = "[resync-status] [0x09] [3] [status] [msd IP] [msd port]";
	       break;
	     case REMOTE_REGISTER:
	    	 argNames = "[remote-register] [message-direction] [10] [char-set] [URS-Identifier] [keyword] [host URI] [expiration time] [Lat] [Long] [network type] [stream type] [inversion flag]";
	       break;
	     case INVALIDATE:
	    	 if(numFields==6)
	    		 argNames = "[invalidate] [0x0A] [6] [char-set] [keyword] [cached IP] [cached port] [IP] [port]";
	    	 else if(numFields==7)
	    		 argNames = "[invalidate] [0x0A] [7] [char-set] [keyword] [cached IP] [cached port] [IP] [port] [inversion flag]";
	       break;
	     case SEARCH:
	    	 argNames = "[search] [0x0A] [3] [char-set] [parameter] [client port]";
	       break;
	     case REDIRECT:
	    	 if(numFields==5)
	    		 argNames = "[redirect] [0x07] [5] [char-set] [keyword] [msd IP] [msd port] [hop count]";
	    	 else if(numFields==6)
	    		 argNames = "[redirect] [0x07] [6] [char-set] [inversion flag] [keyword] [msd IP] [msd port] [hop count]";  
	     case EXT_SEARCH:
	    	 argNames = "[ext-search] [0x0A] [5] [char-set] [parameter] [IP] [port] [inversion flag]";
	       break;
	     case EXT_SEARCH_INVALID:
	    	 argNames = "[ext-search-invalid] [0x08] [2] [char-set] [keyword]";
	       break;
	     case SEARCH_RESPONSE:
	    	 if(numFields==11)
	    		 argNames = "[search-response] [0x07] [11] [char-set] [global] [keyword] [domain URI] [URS Identifier] [expiration time] [Lat] [Long] [network type] [stream type] [hop count]";
	    	 else if(numFields==17)
	    		 argNames = "[search-response] [0x07] [17] [char-set] [local] [keyword] [channel IP] [channel port] [channel scope] [Geographical CN] [Lat] [Long] [network type] [source IP] [stream type] [preferred application] [CLI argument] [unicast IP] [unicast port] [hop count]";  
	       break;
	     case EXT_SEARCH_RESPONSE:
	    	 argNames = "[ext-search-response] [0x08] [11] [char-set] [global] [keyword] [domain URI] [URS Identifier] [expiration time] [Lat] [Long] [network type] [stream type] [hop count]";
	       break;
	     case TX_END:
	    	 argNames = "[tx-end] [message direction] [3] [char-set] [keyword] [tag]";
	       break;
	     case GET_BACKUP_MSD:
	    	 argNames = "[get-backup-msd] [0x0A] [4] [char-set] [keyword] [IP] [port]";
	       break;
	     case MSD_PROBE:
	    	 argNames = "[msd-probe] [message-direction] [6] [char-set] [keyword] [msd IP] [msd port] [hop count] [inversion flag]";
	       break;
	     case MSD_PROBE_REPLY:
	    	 argNames = "[msd-probe-reply] [0x0B] [6] [char-set] [keyword] [msd IP] [msd port] [hop count] [inversion flag]";
	       break;
	     case HELLO:
	    	 argNames = "[hello] [0x0B] [5] [domain count] [ID hash] [msd IP] [msd port] [pmcast status]";
	       break;
	     case REP_HELLO:
	    	 argNames = "[rep-hello] [message direction] [3] [ID hash] [address start] [address end]";
	       break;
	     case ADD_SPACE:
	    	 argNames = "[add-space] [message direction] [4] [range start] [range end] [significant bits] [ID hash]";
	       break;
	     
		}
		
		if(argNames!=null && argNames.length()>0)
		{
			String myArgNames = argNames;
			
			myArgNames = myArgNames.replace("] [", ",");
			myArgNames = myArgNames.replace("[", "");
			myArgNames = myArgNames.replace("]", "");
	        argNamesArray = Helpers.extractFields(myArgNames, ",");
	        argNamesArray = Arrays.copyOfRange(argNamesArray, 3, argNamesArray.length);
	        
	        String regex = anyField(argNamesArray.length);
	        
			Pattern p = Pattern.compile(regex,
	    		Pattern.CASE_INSENSITIVE | Pattern.DOTALL);
		    
		    Matcher m = p.matcher(restOfMessage);
		    if (m.find())
		    {
		    	for(int i=0; i<argNamesArray.length; i++)
		        {
		        	arguments.put("["+argNamesArray[i]+"]",m.group(1+i*2));
		        }
			
		    	result = true;
		    }
		    else
		    {
		    	result = false;
		    }
		}
		return result;
	}
	
	private String anyField(int numFieldHolders)
	{
		String regex = "";
		for(int i=0; i<numFieldHolders-1; i++)
			regex += argPlusWS;
		regex += arg;
		return regex;
		
	}

	private int parseNumFields(String numFields_m) {
		int numFieldsLocal = 0;
		try{
			numFieldsLocal = Integer.parseInt(numFields_m);
		}
		catch(NumberFormatException e){
			numFieldsLocal = -1;
		}
		return numFieldsLocal;
	}

	private MessageDirection parseDirection(String dirDigits_m) {
		dirDigits_m = dirDigits_m.toUpperCase();
		if(dirDigits_m.equals("00"))
			return MessageDirection.Client_MSD_0x00;
		else if(dirDigits_m.equals("01"))
			return MessageDirection.Client_URS_0x01;
		else if(dirDigits_m.equals("02"))
			return MessageDirection.MSD_URS_0x02;
		else if(dirDigits_m.equals("03"))
			return MessageDirection.URS_Client_0x03;
		else if(dirDigits_m.equals("04"))
			return MessageDirection.MSD_MSD_local_0x04;
		else if(dirDigits_m.equals("05"))
			return MessageDirection.URS_URS_0x05;
		else if(dirDigits_m.equals("06"))
			return MessageDirection.MSD_MSD_cmcast_0x06;
		else if(dirDigits_m.equals("07"))
			return MessageDirection.MSD_client_udp_0x07;
		else if(dirDigits_m.equals("08"))
			return MessageDirection.MSD_client_unicast_0x08;
		else if(dirDigits_m.equals("09"))
			return MessageDirection.URS_MSD_0x09;
		else if(dirDigits_m.equals("0A"))
			return MessageDirection.Client_MSD_0x0A;
		else if(dirDigits_m.equals("0B"))
			return MessageDirection.MSD_MSD_unicast_0x0B;
		else
			return MessageDirection.UNKNOWN;
	}

	private MessageType parseMessageType(String messageType_m) {
		messageType_m = messageType_m.toUpperCase();
		if(messageType_m.equals("CHECK"))
			return MessageType.CHECK;
		else if(messageType_m.equals("CHECK-RESPONSE"))
			return MessageType.CHECK_RESPONSE;
		else if(messageType_m.equals("REGISTER"))
			return MessageType.REGISTER;
		else if(messageType_m.equals("REGISTER-STATUS"))
			return MessageType.REGISTER_STATUS;
		else if(messageType_m.equals("QUERY"))
			return MessageType.QUERY;
		else if(messageType_m.equals("QUERY-RESPONSE"))
			return MessageType.QUERY_RESPONSE;
		else if(messageType_m.equals("REQUEST"))
			return MessageType.REQUEST;
		else if(messageType_m.equals("REQUEST-RESPONSE"))
			return MessageType.REQUEST_RESPONSE;
		else if(messageType_m.equals("BYE"))
			return MessageType.BYE;
		else if(messageType_m.equals("UNICAST-UPDATE"))
			return MessageType.UNICAST_UPDATE;
		else if(messageType_m.equals("UNICAST-UPDATE-STATUS"))
			return MessageType.UNICAST_UPDATE_STATUS;
		else if(messageType_m.equals("RESYNC"))
			return MessageType.RESYNC;
		else if(messageType_m.equals("RESYNC-STATUS"))
			return MessageType.RESYNC_STATUS;
		else if(messageType_m.equals("REMOTE-REGISTER"))
			return MessageType.REMOTE_REGISTER;
		else if(messageType_m.equals("INVALIDATE"))
			return MessageType.INVALIDATE;
		else if(messageType_m.equals("SEARCH"))
			return MessageType.SEARCH;
		else if(messageType_m.equals("REDIRECT"))
			return MessageType.REDIRECT;
		else if(messageType_m.equals("EXT-SEARCH"))
			return MessageType.EXT_SEARCH;
		else if(messageType_m.equals("EXT-SEARCH-INVALID"))
			return MessageType.EXT_SEARCH_INVALID;
		else if(messageType_m.equals("SEARCH-RESPONSE"))
			return MessageType.SEARCH_RESPONSE;
		else if(messageType_m.equals("EXT-SEARCH-RESPONSE"))
			return MessageType.EXT_SEARCH_RESPONSE;
		else if(messageType_m.equals("TX-END"))
			return MessageType.TX_END;
		else if(messageType_m.equals("GET-BACKUP-MSD"))
			return MessageType.GET_BACKUP_MSD;
		else if(messageType_m.equals("MSD-PROBE"))
			return MessageType.MSD_PROBE;
		else if(messageType_m.equals("MSD-PROBE-REPLY"))
			return MessageType.MSD_PROBE_REPLY;
		else if(messageType_m.equals("HELLO"))
			return MessageType.HELLO;
		else if(messageType_m.equals("REP-HELLO"))
			return MessageType.REP_HELLO;
		else if(messageType_m.equals("ADD-SPACE"))
			return MessageType.ADD_SPACE;
		else
			return MessageType.UNKNOWN;
				
	}
	
	
//	CHECK, CHECK_RESPONSE, REGISTER, REGISTER_STATUS, 
//	QUERY, QUERY_RESPONSE, REQUEST, REQUEST_RESPONSE, BYE, 
//	UNICAST_UPDATE, UNICAST_UPDATE_STATUS, RESYNC, RESYNC_STATUS, 
//	REMOTE_REGISTER, INVALIDATE, SEARCH, REDIRECT, EXT_SEARCH, 
//	EXT_SEARCH_INVALID, SEARCH_RESPONSE, EXT_SEARCH_RESPONSE, 
//	TX_END, GET_BACKUP_MSD, RESYNC_RESPONSE, MSD_PROBE, MSD_PROBE_REPLY, 
//	HELLO, REP_HELLO, ADD_SPACE
	
}
